package priv.wangy.micro.common.sync;

import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import priv.wangy.micro.common.config.appnode.AppNodeProperties;
import priv.wangy.micro.common.sync.config.DataSyncProperties;
import priv.wangy.micro.common.sync.constant.DataSyncObjectEnum;
import priv.wangy.micro.common.sync.consumer.ConsumerContainer;
import priv.wangy.micro.common.sync.consumer.DataSyncConsumer;
import priv.wangy.micro.common.sync.consumer.IDataSyncConsumer;
import priv.wangy.micro.common.sync.producer.AbstractDataSyncProducer;
import priv.wangy.micro.common.sync.producer.DataSyncProducer;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * @author zhangjq
 * @since 2023-12-18
 */
@Component
public class DataSyncFactory {

    private final DataSyncProperties dataSyncProperties;

    private final AppNodeProperties appNodeProperties;

    private final BeanFactory beanFactory;

    private final AmqpAdmin amqpAdmin;

    private final ConnectionFactory connectionFactory;

    public DataSyncFactory(DataSyncProperties dataSyncProperties, AppNodeProperties appNodeProperties, BeanFactory beanFactory, AmqpAdmin amqpAdmin, ConnectionFactory connectionFactory) {
        this.dataSyncProperties = dataSyncProperties;
        this.appNodeProperties = appNodeProperties;
        this.beanFactory = beanFactory;
        this.amqpAdmin = amqpAdmin;
        this.connectionFactory = connectionFactory;
    }

    private final Map<String, FanoutExchange> exchangeMap = new HashMap<>();

    private final Map<String, Queue> queueMap = new HashMap<>();

    private final static String DEFAULT_QUEUE = "queue";

    private final Map<String, AbstractDataSyncProducer> producers = new HashMap<>();

    private final Map<String, IDataSyncConsumer> consumers = new HashMap<>();

    private final Map<String, Boolean> dataSyncMap = new HashMap<>();

    private FanoutExchange getExchange(DataSyncObjectEnum sync) {
        FanoutExchange exchange = exchangeMap.get(sync.toString());
        if (Objects.isNull(exchange)) {
            exchange = new FanoutExchange(sync.getExchangeName());
            amqpAdmin.declareExchange(exchange);
            exchangeMap.put(sync.toString(), exchange);
        }
        return exchange;
    }

    private Queue getQueue(FanoutExchange exchange) {
        Queue queue = queueMap.get(DEFAULT_QUEUE);
        if (Objects.isNull(queue)) {
            queue = new Queue(String.format("whmw.edu.%s.queue", appNodeProperties.getAppNode()), false, true, true);
            queueMap.put(DEFAULT_QUEUE, queue);
        }
        Binding binding = BindingBuilder.bind(queue).to(exchange);
        this.amqpAdmin.declareQueue(queue);
        this.amqpAdmin.declareBinding(binding);
        return queue;
    }

    private Queue getQueue(DataSyncObjectEnum sync, FanoutExchange exchange) {
        Queue queue = queueMap.get(sync.toString());
        if (Objects.isNull(queue)) {
            queue = new Queue(sync.getQueueName(appNodeProperties.getAppNode()), false, true, true);
            Binding binding = BindingBuilder.bind(queue).to(exchange);
            this.amqpAdmin.declareQueue(queue);
            this.amqpAdmin.declareBinding(binding);
            queueMap.put(sync.toString(), queue);
        }
        return queue;
    }

    private AbstractDataSyncProducer getProducer(DataSyncObjectEnum sync) {
        synchronized (producers) {
            AbstractDataSyncProducer producer = producers.get(sync.toString());
            if (Objects.isNull(producer)) {
                FanoutExchange exchange = getExchange(sync);
                DataSyncProperties.ProducerParams params = dataSyncProperties.getProducer(sync.toString());
                if (Objects.nonNull(params) && StringUtils.hasLength(params.getProducer())) {
                    producer = beanFactory.getBean(params.getProducer(), AbstractDataSyncProducer.class);
                } else {
                    producer = beanFactory.getBean(DataSyncProducer.class);
                }
                producer.setExchange(sync.getExchangeName());
                producers.put(sync.toString(), producer);
            }
            return producer;
        }
    }

    public void setConsumer(DataSyncObjectEnum sync) {
        synchronized (consumers) {
            IDataSyncConsumer consumer = consumers.get(sync.toString());
            if (Objects.isNull(consumer)) {
                FanoutExchange exchange = getExchange(sync);

                DataSyncProperties.ConsumerParams params = dataSyncProperties.getConsumer(sync.toString());
                Queue queue;
                if (Objects.nonNull(params) && StringUtils.hasLength(params.getConsumer())) {
                    queue = getQueue(sync, exchange);
                    consumer = beanFactory.getBean(params.getConsumer(), IDataSyncConsumer.class);
                } else {
                    queue = getQueue(exchange);
                    consumer = beanFactory.getBean(DataSyncConsumer.class);
                }
                ConsumerContainer container = ConsumerContainer.builder()
                        .amqpAdmin(amqpAdmin).connectionFactory(connectionFactory).queue(queue).consumer(consumer).build();
                SimpleMessageListenerContainer listener = container.getObject();
                if (Objects.nonNull(listener)) {
                    listener.start();
                    consumers.put(sync.toString(), consumer);
                }
            }
        }
    }

    public void noticeUpdated(DataSyncObjectEnum sync) {
        AbstractDataSyncProducer producer = getProducer(sync);
        producer.notice();
    }

    public void waitingSync(DataSyncObjectEnum sync) {
        dataSyncMap.put(sync.toString(), Boolean.TRUE);
    }

    public void completedSync(DataSyncObjectEnum sync) {
        dataSyncMap.put(sync.toString(), Boolean.FALSE);
    }

    public boolean isWaitingSync(DataSyncObjectEnum sync) {
        setConsumer(sync);
        return Optional.ofNullable(dataSyncMap.get(sync.toString())).orElse(false);
    }
}
